package com.flying.framework.data;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson.annotation.JSONField;
import com.flying.common.util.CollectionUtils;
import com.flying.common.util.DateUtils;
import com.flying.common.util.JSONUtils;
import com.flying.common.util.StringUtils;
import com.flying.common.util.Utils;

/**
 * @author wanghaifeng
 * 
 */
public class Data implements Serializable {
	private static final long serialVersionUID = -8662885273436542677L;
	@JSONField(serialize=false)
	private final Map<String, Object> values = Utils.newHashMap();

	public Data(Map<String, Object> values) {
		if(values != null) {
			this.values.putAll(values);
		}
	}

	public Data(Object ...params  ) {
		if(params != null) {
			for(int i=0; i < params.length / 2; i++) {
				if(2 * i  + 1 >= params.length) break;
				String k = (String)params[2 * i];
				Object v = params[ 2* i + 1];
				this.values.put(k, v);
			}
		}
	}
	
	public Map<String, Object> getValues() {
		return values;
	}

	@SuppressWarnings("unchecked")
	public <T> T get(String key) {
		return (T) values.get(key);
	}

	@SuppressWarnings("unchecked")
	public <T> T get(String key, T def) {
		if(this.contains(key))
			return (T)values.get(key);
		else
			return def;
	}
	
	public final <T> T get(String key, Class<T> clazz) {
		Object o = get(key);
		if(o == null) {
			return null;
		}
		if(o instanceof BigDecimal) {
			if(clazz == Double.class) {
				Double d =((BigDecimal)o).doubleValue();
				return (T)d;
			} else if(clazz == Float.class) {
				Float d =((BigDecimal)o).floatValue();
				return (T)d;
			} else if(clazz == Integer.class) {
				Integer d =((BigDecimal)o).intValue();
				return (T)d;
			} else if(clazz == Long.class) {
				Long d =((BigDecimal)o).longValue();
				return (T)d;
			}
		} else if(o instanceof Long && clazz == Integer.class) {
			Integer x = ((Long)o).intValue();
			return (T)x;
		}
		return (T)o;
	}

	@SuppressWarnings("unchecked")
	public final <T> T remove(String key) {
		return (T)values.remove(key);
	}

	public final <T> Data put(String key, T value) {
		values.put(key, value);
		return this;
	}

	public <T> Data putAll(Map<String, T> values) {
		if(values != null)
			this.values.putAll(values);
		return this;
	}

	public final <T> Data putAll(Object ...params  ) {
		if(params != null) {
			for(int i=0; i < params.length / 2; i++) {
				if(2 * i  + 1 >= params.length) break;
				String k = (String)params[2 * i];
				Object v = params[ 2* i + 1];
				this.put(k, v);
			}
		}
		return this;
	}

	public Data putAll(Data val) {
		if(val != null)
			this.putAll(val.values);
		return this;
	}

	public Set<String> keys() {
		return values.keySet();
	}

	public Collection<Object> values() {
		return values.values();
	}

	public boolean contains(String key) {
		return values.containsKey(key);
	}

	public boolean isEmpty() {
		return values.isEmpty();
	}

	public String getString(String key, String def) {
		return this.convert(key, String.class, (x) -> (x), def);
	}
	
	public String getString(String key) {
		return this.convert(key, String.class, (x) -> (x), null);
	}

	@SuppressWarnings("rawtypes")
	public String[] getStrings(String key) {
		Object v = get(key);
		if(v == null) return new String[]{};;
		if (v instanceof String) 
			return new String[]{(String)v};
		if (v.getClass().isArray()) {
			String[] res = new String[Array.getLength(v)];
			for(int i=0; i< res.length; i++) {
				Object x = Array.get(v, i);
				//
				if(x == null) {
					res[i] = null;
				} else if(x instanceof String) {
					res[i] = (String)x;
				} else {
					res[i] = x.toString();
				}
			}
			return res;
		}
		if (v instanceof Collection) {
			Collection c = (Collection) v;
			String[] vs = new String[c.size()];
			int i = 0;
			for (Iterator it = c.iterator(); it.hasNext();) {
				Object iv = it.next();
				vs[i] = iv == null ? null : iv.toString();
				i++;
			}
			return vs;
		}
		return new String[]{v.toString()};
	}

	public Boolean getBoolean(String key, Boolean def) {
		return this.convert(key, Boolean.class, (x) -> ("true".equalsIgnoreCase(x)), def);
	}
	
	public Boolean getBoolean(String key) {
		return this.convert(key, Boolean.class, (x) -> ("true".equalsIgnoreCase(x)), null);
	}

	public Boolean[] getBooleans(String key) {
		List<Boolean> list = convertAsList(key, Boolean.class, (x) -> ("true".equalsIgnoreCase(x)));
		return list.toArray(Boolean[]::new);
	}
	
	public Integer getInteger(String key, Integer def) {
		return this.convert(key, Integer.class, (x) -> (Integer.parseInt(x)), def);
	}
	
	public Integer getInteger(String key) {
		return this.getInteger(key, null);
	}

	public Integer[] getIntegers(String key) {
		List<Integer> list = convertAsList(key, Integer.class, (x) -> (Integer.parseInt(x)));
		return list.toArray(Integer[]::new);
	}
	public Long getLong(String key, Long def) {
		return this.convert(key, Long.class, (x) -> (Long.parseLong(x)), def);
	}

	public Long getLong(String key) {
		return this.getLong(key, null);
	}

	public Long[] getLongs(String key) {
		List<Long> list = convertAsList(key, Long.class, (x) -> (Long.parseLong(x)));
		return list.toArray(Long[]::new);
	}
	
	public Double getDouble(String key, Double def) {
		return this.convert(key, Double.class, (x) -> (Double.parseDouble(x)), def);
	}

	public Double getDouble(String key) {
		return this.getDouble(key, null);
	}

	public Double[] getDoubles(String key) {
		List<Double> list = convertAsList(key, Double.class, (x) -> (Double.parseDouble(x)));
		return list.toArray(Double[]::new);
	}
	
	public Float getFloat(String key, Float def) {
		return this.convert(key, Float.class, (x) -> (Float.parseFloat(x)), def);
	}

	public Float getFloat(String key) {
		return this.getFloat(key, null);
	}

	public Float[] getFloats(String key) {
		List<Float> list = convertAsList(key, Float.class, (x) -> (Float.parseFloat(x)));
		return list.toArray(Float[]::new);
	}
	
	public Date getDate(String key) {
		return this.convert(key, Date.class, (x) -> (DateUtils.parseDate(x)), null);
	}

	public Date[] getDates(String key) {
		List<Date> list = convertAsList(key, Date.class, (x) -> (DateUtils.parseDate(x)));
		return list.toArray(Date[]::new);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Map<String, Object> getMap(String key) {
		Object val = this.get(key);
		if(val == null) return null;
		//
		if(val instanceof String) {
			return JSONUtils.toMap((String)val);
		} else {
			return (Map)val;
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Map<String, Object>[] getMaps(String key) {
		Object val = this.get(key);
		if(val == null) return null;
		//
		if(val instanceof String)
			return JSONUtils.toArray((String)val);
		if(val.getClass().isArray()) {
			Map<String, Object>[] res = new Map[Array.getLength(val)];
			for(int i=0; i< Array.getLength(val); i++) {
				Map<String, Object> map = (Map)Array.get(val, i);
				res[i] = map;
			}
			return res;
		} else {
			throw new java.lang.IllegalArgumentException("Data.getMaps value of Key[" + key +"] not Map<String, Object>");
		}
	}
	
	public List<Map<String, Object>> getRows(String key) {
		List<Map<String, Object>> list = this.get(key);
		return list;
	}

	@SuppressWarnings("unchecked")
	public Data getValue(String key) {
		Object v = get(key);
		if (v == null)
			return null;
		if (v instanceof Data)
			return (Data) v;
		if (v instanceof Map)
			return (new Data((Map<String, Object>) v));
		return null;
	}

	public Map<String, Object> filterValues(DataFilter filter) {
		if (filter == null)
			return null;
		Map<String, Object> res = Utils.newHashMap();
		for (String key : values.keySet()) {
			if (!filter.isValid(key, values.get(key)))
				res.put(key, values.remove(key));
		}
		return res;
	}

	public Data merge(Data that, boolean overrides) {
		return this.merge(that, null, overrides);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Data merge(Data that, String[] fields, boolean overrides) {
		if(that == null) return this;
		
		Set<String> fieldset = Utils.newHashSet();
		if(fields != null) fieldset.addAll(CollectionUtils.arrayToList(fields));
		
		for (String key : that.keys()) {
			if(!fieldset.isEmpty() && !fieldset.contains(key))
				continue;
			Object thisValue = this.get(key);
			Object thatValue = that.get(key);
			if (this.contains(key)) {
				if ((thisValue instanceof Map || thisValue instanceof Data) && (thatValue instanceof Map || thatValue instanceof Data)) {
					Data thisData = thisValue instanceof Data ? (Data) thisValue : new Data((Map<String, Object>) thisValue);
					Data thatData = thatValue instanceof Data ? (Data) thatValue : new Data((Map<String, Object>) thatValue);
					thisData.merge(thatData, overrides);
					if(thisValue instanceof Map) {
						this.put(key, thisData.getValues());
					} 
				} else if(thisValue instanceof Collection && thatValue instanceof Collection){
					((Collection)thisValue).addAll((Collection)thatValue);
				}else {
					if (overrides) {
						this.put(key, thatValue);
					}
				}
			} else {
				this.put(key, thatValue);
			}
		}
		return this;
	}
	
	public <T> T as(Class<T> type) throws Exception {
		return DataUtils.convert(this, type);
	}

	public String toString() {
		return StringUtils.toString(this.values);
	}
	
	interface Convert <T> {
		T convert(String x);
	}
	
	@SuppressWarnings("unchecked")
	private <T> List<T> convertAsList(String key, Class<T> c, Convert<T> f) {
		final List<T> list = Utils.newArrayList();
		Object v = get(key);
		if(v == null) return list;
		if (!v.getClass().isArray()) {
			if(v.getClass() == c) {
				list.add((T)v);
			} else {
				list.add(f.convert(v.toString()));
			}
		} else {
			for(int i=0; i< Array.getLength(v); i++) {
				Object x = Array.get(v, i);
				if(x == null) {
					continue;
				}
				//
				if(x.getClass() == c) {
					list.add((T)x);
				} else {
					list.add(f.convert(x.toString()));
				}
			}
		}
		return list;
	}
	
	@SuppressWarnings("unchecked")
	private <T> T convert(String key, Class<T> c, Convert<T> f, T def) {
		Object v = get(key);
		if(v == null) return def;
		try {
			if (!v.getClass().isArray()) {
				if(v.getClass() == c) {
					return (T)v;
				} else {
					return f.convert(v.toString());
				}
			} else {
				for(int i=0; i< Array.getLength(v); i++) {
					Object x = Array.get(v, i);
					if(x == null) {
						continue;
					}
					//
					if(x.getClass() == c) {
						return (T)x;
					} else {
						return f.convert(x.toString());
					}
				}
			}
		}catch (Exception e) {
			System.out.println(e+":"+v);
		}
		return def;
	}
}
